package proxy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;

import simulation.Simulator;
import consumer.Viewing;

/**
 * 
 * Reference Scenario, units are routed on the shortest path through the network
 * 
 * @author Anita Sobe, Wilfried Elmenreich
 * 
 */
public class ShortestPathServerNode extends ServerNode {

	protected HashMap<Integer, Double> fetchTimeOut;
	protected ShortestPathRouting shortestPathRouting;
	protected TreeMap<Integer, Integer> requestCount;

	public ShortestPathServerNode(int id, Simulator simulator,
			Configuration config, HormoneControls hCtrl) {
		super(id, simulator, config, hCtrl);
		if (config.isDirectDownload()) {
			this.shortestPathRouting = new ShortestPathRoutingDirectDownload(
					simulator);
		} else {
			this.shortestPathRouting = new ShortestPathRouting(simulator);
		}
		this.fetchTimeOut = new HashMap<Integer, Double>();
		this.requestCount = new TreeMap<Integer, Integer>();
	}

	@Override
	public void migrateVideoUnits() {

		// check for finished migrations
		// outgoing units
		super.cleanupMigrations();

		// incoming units
		for (Iterator<UnitContainer> iter = incomingUnits.listIterator(); iter
				.hasNext();) {
			UnitContainer uc = iter.next();
			if (simulator.getSimTime() >= uc.copyProcessUntil) {
				if (searchPresentUnit(uc.getVideoUnit().getId()) == null) {
					simulator.log("u" + uc.getVideoUnit().getId()
							+ " has arrived at S" + getId());
					super.addUnitToStorage(uc); // unit has arrived
					// reset usage to current time
					uc.setTimeReceived(simulator.getSimTime());
					uc.viewedUntil = simulator.getSimTime();
					uc.copyProcessUntil = simulator.getSimTime();
					logUnitList();
					iter.remove();

					// check if this video is to be routed
					if (uc.route != null) {
						simulator.log("reroute");
						shortestPathRouting.routeUnit(uc.route, uc
								.getVideoUnit().getId());
						uc.route = null; // the active route continues to live
						// in the incoming unit
					}
				}
			}
		}
	}

	/**
	 * 
	 * @param currentlyDemanding
	 * @param newRequest
	 */
	public void increaseInterest(List<Viewing> currentlyDemanding,
			boolean newRequest, boolean sequential) {

		if (currentlyDemanding.size() == 0)
			return;

		// sequential requests only next unit in sequence
		if (config.isPrioritizeTransmission()) {
			shortestPathRouting.fetchOnShortestRoute(this, currentlyDemanding
					.get(0).getVideoUnit().getId());
		} else {
			// all units in parallel
			for (Viewing vi : currentlyDemanding) {
				shortestPathRouting.fetchOnShortestRoute(this, vi
						.getVideoUnit().getId());
			}
		}

	}

	public HashMap<Integer, Double> getFetchTimeOut() {
		return fetchTimeOut;
	}

	@Override
	protected void putMigrationsToOutgoing() {
		// nothing to be done here
		return;
	}

	@Override
	boolean performThreeWayCleanupForUnit(int id) {
		UnitContainer uc = searchPresentUnit(id);
		if (uc == null)
			return false;

		// try to delete if a another copy is available in the system
		if (super.countUnitOnNeighbors(uc.getVideoUnit().getId()) > 1) {
			// remove this unit
			removeUnitFromStorage(uc);
			simulator.log("S" + id + ": removed u" + uc.getVideoUnit().getId());
			logUnitList();
			return true; // was immediately deleted
		}

		// the unit cannot be deleted, but perhaps we can move it to another
		// server

		// did not work, try to push this unit to a server with more free space
		Link bestlink = null;
		int myFreeSpace = diskFreeinBytes();
		int largestFreeSpace = myFreeSpace + uc.getVideoUnit().getSizeInBytes();
		for (Link li : links) {
			if ((li.getOccupiedUntil() - simulator.getSimTime() > hCtrl
					.getBusyLinkThreshold())
					|| (li.getTargetNode().diskFreeinBytes() < uc
							.getVideoUnit().getSizeInBytes()))
				continue;
			if (li.getTargetNode().searchInIncomingUnits(
					uc.getVideoUnit().getId()) != null
					|| li.getTargetNode().searchPresentUnit(
							uc.getVideoUnit().getId()) != null)
				continue;
			int targetFreeSpace = li.getTargetNode().diskFreeinBytes();
			if (targetFreeSpace > largestFreeSpace) {
				largestFreeSpace = targetFreeSpace;
				bestlink = li;
			}
		}
		if (bestlink != null) {

			Migration m1 = new Migration(uc.getVideoUnit(), bestlink, false,
					0.0);

			if (!getOutgoingUnits().get(links.indexOf(bestlink)).contains(m1)) {
				getOutgoingUnits().get(links.indexOf(bestlink)).add(m1);
				Collections.sort(getOutgoingUnits()
						.get(links.indexOf(bestlink)));
				Migration mC = getOutgoingUnits().get(links.indexOf(bestlink))
						.get(0);
				if (migrateUnitTo(mC.getVideoUnit(), mC.getLink(), mC.isCopy()))
					simulator.log("S" + this.getId()
							+ ": cleanup moving(diskcap) u"
							+ mC.getVideoUnit().getId() + " to S"
							+ bestlink.getTargetNode().getId() + ":");
				return false;
			}
		}
		return false;
	}

	public void setFetchTimeOut(HashMap<Integer, Double> fetchTimeOut) {
		this.fetchTimeOut = fetchTimeOut;
	}

	private void initUnitRequestList() {
		for (VideoUnit vu : simulator.getVideoUnitDirectory()) {
			requestCount.put(vu.getId(), 0);
		}
	}

	@Override
	public void countRequest(ArrayList<Viewing> viewings) {
		if (requestCount.size() == 0) {
			initUnitRequestList();
		}
		for (Viewing v : viewings) {
			int current = v.getVideoUnit().getId();
			int currentCount = requestCount.get(current) + 1;
			requestCount.put(current, currentCount);
		}
	}

	@Override
	public void decreaseInterest(Viewing vi) {
		return;
	}
}
